package com.collabscm.pf.web.ext;

import java.util.List;

import com.collabscm.pf.exception.ValidationFailure;
import com.collabscm.pf.logging.Logger;
import com.collabscm.pf.logging.LoggerFactory;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.ExceptionHolder;

/**
 * 异常拦截器，主要是覆盖了XWork提供的ExceptionMappingInterceptor。
 *
 * @author Zeng Raoxing
 */
public class ExceptionInterceptor extends AbstractInterceptor {
	private static final long serialVersionUID = 1L;
	/**
	 * 日志实例
	 */
	private static final Logger logger = LoggerFactory.getLogger(ExceptionInterceptor.class);
	/**
	 * 验证错误的Result
	 */
	private String validationFailure = "validationFailure";
	
	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		String result;
        try {
            result = invocation.invoke();
        } catch (Exception e) {
        	if(e instanceof ValidationFailure) {//验证错误，这是非异常，仅以异常作为一个方法的出口而已(走异常通道的非异常)
        		result = validationFailure;
        		invocation.getStack().push(e);
        	} else {
        		logger.error("统一异常捕捉", e);
	            List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
	            String mappedResult = this.findResultFromExceptions(exceptionMappings, e);
	            if (mappedResult != null) {
	                result = mappedResult;
	                publishException(invocation, new ExceptionHolder(e));
	            } else {
	                throw e;
	            }
        	}
        }
        return result;
	}

    private String findResultFromExceptions(List<ExceptionMappingConfig> exceptionMappings, Throwable t) {
        String result = null;
        // Check for specific exception mappings.
        if (exceptionMappings != null) {
            int deepest = Integer.MAX_VALUE;
            for (Object exceptionMapping : exceptionMappings) {
                ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) exceptionMapping;
                int depth = getDepth(exceptionMappingConfig.getExceptionClassName(), t);
                if (depth >= 0 && depth < deepest) {
                    deepest = depth;
                    result = exceptionMappingConfig.getResult();
                }
            }
        }

        return result;
    }

    /**
     * Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if there's no match.
     * Otherwise, returns depth. Lowest depth wins.
     *
     * @param exceptionMapping  the mapping classname
     * @param t  the cause
     * @return the depth, if not found -1 is returned.
     */
    public int getDepth(String exceptionMapping, Throwable t) {
        return getDepth(exceptionMapping, t.getClass(), 0);
    }

    private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
        if (exceptionClass.getName().contains(exceptionMapping)) {
            // Found it!
            return depth;
        }
        // If we've gone as far as we can go and haven't found it...
        if (exceptionClass.equals(Throwable.class)) {
            return -1;
        }
        return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
    }

    /**
     * Default implementation to handle ExceptionHolder publishing. Pushes given ExceptionHolder on the stack.
     * Subclasses may override this to customize publishing.
     *
     * @param invocation The invocation to publish Exception for.
     * @param exceptionHolder The exceptionHolder wrapping the Exception to publish.
     */
    protected void publishException(ActionInvocation invocation, ExceptionHolder exceptionHolder) {
        invocation.getStack().push(exceptionHolder);
    }

}
